﻿using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Xml;
using SdmxMl.Helper;
using SdmxMl.Common;

namespace SdmxMl.Structure
{
    /// <summary>
    /// The CodeRefType provides the structure for a codelist reference. At a minimum,
    /// either a URN value (a valid SDMX Registry URN as specified in teh SDMX Registry
    /// Specification) must be supplied, or a ColdelistAliasRef and a CodeID must be
    /// specified. CodelistAliasRef references an alias assigned in a CodelistRef
    /// element in the containing hierarchical codelist.CodeRef references a code
    /// from the codelist identified at the level of the parent hierarchical codelist.
    /// Codes are arranged in a hierarchy by reference. Note that it is possible to
    /// reference a single code such that it has multiple parents within the hierarchy.
    /// Further, the hierarchy may or may not be a levelled one. CodeID holds the ID of
    /// the code in the codelist referenced by the hierarchical codelist. CodeRef
    /// references a code. LevelRef holds the id of a Level described in the same
    /// parent Hierarchical Codelist. NodeAliasID allows for an ID to be assigned
    /// to the use of the particular code at that specific point in the hierarchy.
    /// This value is unique within the hierarchy being created, and is used to map
    /// the hierarchy against external structures. Version holds the version number
    /// of the referenced code, to support management of complex hierarchies. Along
    /// with this field are the ValidFrom and ValidTo dates, which are inclusive dates
    /// during which the code is valid within the parent hierarchy.
    /// </summary>
    public class CodeRefType : IComparable, IAnnotationList
    {
        #region Properties

        private CodeRefTypeList _codeRefList;
        public CodeRefTypeList CodeRefList
        {
            get { return _codeRefList; }
        }

        /// <summary>
        /// The uri attribute holds a URI that contains a link to a resource with additional information
        /// about the object, such as a web page. This uri is not a SDMX message.
        /// </summary>
        public string Uri { get; set; }
        public string Urn { get; set; }
        public string CodeID { get; set; }
        public string CodelistAliasRef { get; set; }
        public string LevelRef { get; set; }
        public string NodeAliasID { get; set; }
        public string ValidFrom { get; set; }
        public string ValidTo { get; set; }
        public AnnotationList AnnotationList { get; set; }

        protected string _version;
        public string Version
        {
            get { return _version; }
            set
            {
                _version = value;
                if (string.IsNullOrEmpty(_version))
                    _version = "1.0";
            }
        }

        #endregion //Properties

        #region Constructors

        private void Init()
        {
            _codeRefList = new CodeRefTypeList();
            AnnotationList = new AnnotationList();
        }

        public CodeRefType()
        {
            Init();
        }

        public CodeRefType(string cdId, string cdlistAliasRef)
        {
            Init();
            CodeID= cdId;
            CodelistAliasRef = cdlistAliasRef;
        }

        // Helper
        public static CodeRefTypeList ReadRefList(XmlNode nodeArtefact, SdmxNamespaceManager nsmgr)
        {
            CodeRefTypeList crList = new CodeRefTypeList();
            string query = nsmgr.VersionSdmx == SdmxVersion.SDMX_20 ?
                "./structure:CodeRef" : "./structure:HierarchicalCode";
            XmlNodeList nodeList = nodeArtefact.SelectNodes(query, nsmgr);
            if (nodeList.Count > 0)
            {
                foreach (XmlNode nd in nodeList)
                {
                    CodeRefType cr = new CodeRefType(nd, nsmgr);
                    crList.Add(cr);
                }
            }

            return crList;
        }

        public CodeRefType(XmlNode nodeArtefact, SdmxNamespaceManager nsmgr)
        {
            AnnotationList = new AnnotationList();

            if (nsmgr.VersionSdmx == SdmxVersion.SDMX_20)
            {
                Urn = Xmlhelper.GetNodeText(nodeArtefact, "./structure:URN", nsmgr);
                CodelistAliasRef = Xmlhelper.GetNodeText(nodeArtefact, "./structure:CodelistAliasRef", nsmgr);
                CodeID = Xmlhelper.GetNodeText(nodeArtefact, "./structure:CodeID", nsmgr);

                _codeRefList = ReadRefList(nodeArtefact, nsmgr);

                LevelRef = Xmlhelper.GetNodeText(nodeArtefact, "./structure:LevelRef", nsmgr);
                NodeAliasID = Xmlhelper.GetNodeText(nodeArtefact, "./structure:NodeAliasID", nsmgr);
                Version = Xmlhelper.GetNodeText(nodeArtefact, "./structure:Version", nsmgr);
                ValidFrom = Xmlhelper.GetNodeText(nodeArtefact, "./structure:ValidFrom", nsmgr);
                ValidTo = Xmlhelper.GetNodeText(nodeArtefact, "./structure:ValidTo", nsmgr);
                ValidFrom = ValidFrom.Replace('.', '-');
                ValidTo = ValidTo.Replace('.', '-');
            }
            else
            {
                Urn = Xmlhelper.GetAtt(nodeArtefact, "urn");
                Uri = Xmlhelper.GetAtt(nodeArtefact, "uri");
                Version = Xmlhelper.GetAtt(nodeArtefact, "version");
                ValidFrom = Xmlhelper.GetAtt(nodeArtefact, "validFrom");
                ValidTo = Xmlhelper.GetAtt(nodeArtefact, "validTo");
                string dummyId = Xmlhelper.GetAtt(nodeArtefact, "id");
                CodelistAliasRef = Xmlhelper.GetNodeText(nodeArtefact, "./structure:CodelistAliasRef", nsmgr);
                XmlNode ndCode = nodeArtefact.SelectSingleNode("./structure:CodeID/Ref", nsmgr);
                CodeID = Xmlhelper.GetAtt(ndCode, "id");

                _codeRefList = ReadRefList(nodeArtefact, nsmgr);

            }
        }

        #endregion // Constructors

        #region XML Representation

        private void WriteSdmx_2_1(XmlTextWriter xw, string prefix, ref int aliasId, LevelTypeList levelList, int levelOrder)
        {
            string levelId = string.Empty;
            bool manageLevels = levelList != null && levelList.Count > 0;
            if (manageLevels)
                levelId = levelList.GetIdOfOrder(levelOrder);

            xw.WriteStartElement(prefix, "HierarchicalCode", null);
            // id required
            xw.WriteAttributeString("id", "A" + aliasId.ToString());
            ++aliasId;

            Xmlhelper.WriteAttributeIfExist(xw, "validFrom", Xmlhelper.XsDateTime(ValidFrom));
            Xmlhelper.WriteAttributeIfExist(xw, "validTo", Xmlhelper.XsDateTime(ValidTo));
            if (Version != "1.0")
                Xmlhelper.WriteAttributeIfExist(xw, "version", Version);
            Xmlhelper.WriteAttributeIfExist(xw, "urn", Urn);
            Xmlhelper.WriteAttributeIfExist(xw, "uri", Uri);

            AnnotationList.WriteXml(xw, "common", null);
            xw.WriteElementString(prefix, "CodelistAliasRef", null, CodelistAliasRef);

            xw.WriteStartElement(prefix, "CodeID", null);
            xw.WriteStartElement("Ref");
            xw.WriteAttributeString("id", CodeID);
            xw.WriteEndElement();
            xw.WriteEndElement();

            foreach (CodeRefType cr in _codeRefList)
                cr.WriteXml(xw, prefix, "CodeRef", ref aliasId, levelList, levelOrder+1);

            if (manageLevels && levelOrder == 1)
            {
                xw.WriteStartElement(prefix, "Level", null);
                xw.WriteStartElement("Ref");
                xw.WriteAttributeString("id", levelId);
                xw.WriteEndElement();
                xw.WriteEndElement();
            }

            xw.WriteEndElement();
            
        }

        private void WriteSdmx_2_0(XmlTextWriter xw, string prefix, string tag, ref int aliasId, LevelTypeList levelList, int levelOrder)
        {
            xw.WriteStartElement(prefix, tag, null);

            Xmlhelper.WriteElementIfExist(xw, prefix, "URN", Urn);
            Xmlhelper.WriteElementIfExist(xw, prefix, "CodelistAliasRef", CodelistAliasRef);
            Xmlhelper.WriteElementIfExist(xw, prefix, "CodeID", CodeID);

            foreach (CodeRefType cr in _codeRefList)
                cr.WriteXml(xw, prefix, "CodeRef", ref aliasId, levelList, levelOrder+1);

            Xmlhelper.WriteElementIfExist(xw, prefix, "LevelRef", LevelRef);
            Xmlhelper.WriteElementIfExist(xw, prefix, "NodeAliasID", NodeAliasID);
            if (Version != "1.0")
                Xmlhelper.WriteElementIfExist(xw, prefix, "Version", Version);
            Xmlhelper.WriteElementIfExist(xw, prefix, "ValidFrom", ValidFrom);
            Xmlhelper.WriteElementIfExist(xw, prefix, "ValidTo", ValidTo);

            xw.WriteEndElement();
        }

        public void WriteXml(XmlTextWriter xw, string prefix, string tag, ref int aliasId, LevelTypeList levelList, int levelOrder)
        {
            if (SdmxNamespaceManager.SdmxTarget == SdmxVersion.SDMX_21)
                WriteSdmx_2_1(xw, prefix, ref aliasId, levelList, levelOrder);
            else // SDMX 2.0
                WriteSdmx_2_0(xw, prefix, tag, ref aliasId, levelList, levelOrder);


        }

        #endregion // XML Representation

        #region String Representation

        public override string ToString()
        {
            return CodelistAliasRef + " - " + CodeID;
        }

        #endregion // String Representation

        #region IComparable Members

        public int CompareTo(object obj)
        {
            CodeRefType cr = obj as CodeRefType;

            if (cr == null)
                return -1;

            int rep = string.Compare(this.CodeID, cr.CodeID, false);
            if (rep == 0)
                rep = string.Compare(this.ValidFrom, cr.ValidFrom, false);
            return rep;
        }

        #endregion

    }

    /// <summary>
    /// Hierarchical code references
    /// </summary>
    public class CodeRefTypeList : List<CodeRefType>
    {
        public string LocateParentFromFullId(string fullId, string clAlias, CodeRefTypeList nhl, string parentId)
        {
            string ret = null;

            foreach (CodeRefType cat in nhl)
            {
                if (cat.CodelistAliasRef == clAlias && cat.CodeID == fullId)
                {
                    ret = parentId;
                    break;
                }
                else
                {
                    string p = LocateParentFromFullId(fullId, clAlias, cat.CodeRefList, cat.CodeID);
                    if (p != null)
                        ret = p;
                }
            }
            return ret;
        }

        public CodeRefType LocateItemFromFullId(string fullId, CodeRefTypeList nhl)
        {
            CodeRefType ret = null;

            foreach (CodeRefType cat in nhl)
            {
                if (cat.CodeID == fullId)
                {
                    ret = cat;
                    break;
                }
                else
                {
                    CodeRefType rep = LocateItemFromFullId(fullId, cat.CodeRefList);
                    if (rep != null)
                        ret = rep;
                }
            }
            return ret;
        }


        public bool DeleteRefcode(CodeRefType crtTarget)
        {
            bool deleted = false;
            foreach (CodeRefType crt in this)
            {
                if (crt == crtTarget)
                {
                    this.Remove(crt);
                    deleted = true;
                    break;
                }
                if (crt.CodeRefList.DeleteRefcode(crtTarget) == true)
                {
                    deleted = true;
                    break;
                }
            }

            return deleted;
        }

        public bool IsCodeRefered(string code, string clAlias)
        {
            bool refered = false;
            foreach (CodeRefType crt in this)
            {
                if (crt.CodeID == code && crt.CodelistAliasRef == clAlias)
                {
                    refered = true;
                    break;
                }
                if (crt.CodeRefList.IsCodeRefered(code, clAlias) == true)
                {
                    refered = true;
                    break;
                }
            }

            return refered;
        }

        public CodeRefTypeList GetCodeRefListOfCode(string code)
        {
            CodeRefTypeList refered = null;
            CodeRefTypeList refc = null;
            foreach (CodeRefType crt in this)
            {
                if (crt.CodeID == code)
                {
                    refered = this;
                    break;
                }
                if ((refc = crt.CodeRefList.GetCodeRefListOfCode(code)) != null)
                {
                    refered = refc;
                    break;
                }
            }

            return refered;
        }

        public CodeType GetCodeOfRef(CodeRefType crt, Dictionary<string, CodeListType> dico)
        {
            CodeType code = null;
            if (dico != null && crt != null && dico.ContainsKey(crt.CodelistAliasRef))
            {
                CodeListType clType = dico[crt.CodelistAliasRef];
                code = clType.CodeList.LocateItem(crt.CodeID) as CodeType;
            }
            return code;             
        }

        public void GetTreeHierarchyAsNodes(TreeNode parentNode,  Dictionary<string, CodeListType> dico)
        {
            foreach (CodeRefType crt in this)
            {
                string name = crt.ToString();
                CodeType code = GetCodeOfRef(crt, dico);
                if (code != null)
                    name = code.ToString();
                if (string.IsNullOrEmpty(crt.NodeAliasID) == false)
                    name += " (" + crt.NodeAliasID + ")";

                TreeNode nd = new TreeNode(name);
                nd.Tag = crt;
                parentNode.Nodes.Add(nd);
                crt.CodeRefList.GetTreeHierarchyAsNodes(nd, dico);
            }
        }
    }


    /// <summary>
    /// Helper For numeric coderef ordering
    /// </summary>
    public class CodeRefCompareNum : IComparer<CodeRefType>
    {
        #region IComparer<CodeRefType> Members

        public int Compare(CodeRefType x, CodeRefType y)
        {
            int rep = 0;
            if (x != null && y != null)
            {
                if (NumHelper.isNumeric(x.CodeID, System.Globalization.NumberStyles.Integer) &&
                    NumHelper.isNumeric(y.CodeID, System.Globalization.NumberStyles.Integer))
                {
                    rep = System.Convert.ToInt32(x.CodeID) - System.Convert.ToInt32(y.CodeID);
                }

                else
                {
                    rep = x.CodeID.CompareTo(y.CodeID);
                }
            }
            return rep;

        }

        #endregion
    }
 
}
